﻿#region Copyright

// // The contents of this file are subject to the Mozilla Public License
// // Version 1.1 (the "License"); you may not use this file except in compliance
// // with the License. You may obtain a copy of the License at
// //   
// // http://www.mozilla.org/MPL/
// //   
// // Software distributed under the License is distributed on an "AS IS"
// // basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// // License for the specific language governing rights and limitations under 
// // the License.
// //   
// // The Initial Developer of the Original Code is Robert Smyth.
// // Portions created by Robert Smyth are Copyright (C) 2008-2013.
// //   
// // All Rights Reserved.

#endregion

using System.Collections.Generic;
using NoeticTools.NWorkflow.DotNetInterop;
using NoeticTools.NWorkflow.ProgressMonitoring;


namespace NoeticTools.NWorkflow
{
    internal class SequentialWorkflowsRunner : IActivity<Outcome>, IActivityListener<Outcome>, IProgress, IStepProgressListener
    {
        private readonly Func<IEnumerable<IWorkflow<Outcome>>> _getWorkflows;
        private readonly IStepRules<Outcome> _step;
        private IWorkflow<Outcome> _currentWorkflow;
        private IActivityListener<Outcome> _listener;
        private Outcome _result = Outcome.Unknown;
        private Queue<IWorkflow<Outcome>> _workflows;

        public SequentialWorkflowsRunner(IStepRules<Outcome> step, Func<IEnumerable<IWorkflow<Outcome>>> getWorkflows)
        {
            _step = step;
            _getWorkflows = getWorkflows;
            SyncRoot = new object();
            ProgressAvailable = true;
            Message = _step.StepRulesName;
        }

        public object SyncRoot { get; private set; }

        public void Begin(IActivityListener<Outcome> listener)
        {
            _listener = listener;
            Completed = 0.0;

            _workflows = new Queue<IWorkflow<Outcome>>(_getWorkflows());

            UpdateWeights(_step.StepRuleProgress.StartingWeight);

            BeginNext();
        }

        public void Cancel()
        {
            _currentWorkflow.Cancel();
            _currentWorkflow = null;
        }

        public void OnEvent(object value)
        {
            _currentWorkflow.OnEvent(value);
        }

        public Outcome End()
        {
            return _result;
        }

        void IStepProgressListener.OnProgress(string stepName, IProgress progress)
        {
            ProgressAvailable = progress.ProgressAvailable;
            Inner = progress;
            UpdateCompleted();
        }

        void IActivityListener<Outcome>.OnProgress(IActivityProgress progress)
        {
        }

        void IActivityListener<Outcome>.OnCompleted(IActivity<Outcome> activity)
        {
            lock (SyncRoot)
            {
                UpdateCompleted();
                _result = _currentWorkflow.End();
                if (_result != Outcome.Succeeded)
                {
                    _workflows.Clear();
                }
                BeginNext();
            }
        }

        public double StartingWeight { get; set; }

        public bool ProgressAvailable { get; private set; }

        public double UpdateWeights(double startingWeight)
        {
            StartingWeight = startingWeight;
            foreach (var workflow in _workflows)
            {
                startingWeight = workflow.Progress.UpdateWeights(startingWeight);
            }
            Total = startingWeight - StartingWeight;
            _step.StepRuleProgress.Total = Total;
            return startingWeight;
        }

        public IProgress Inner { get; set; }
        public string Message { get; set; }

        public double AccumulatedCompleted
        {
            get { return (_currentWorkflow == null) ? StartingWeight + Total : _currentWorkflow.Progress.AccumulatedCompleted; }
        }

        public double Total { get; set; }
        public double Completed { get; set; }

        private void BeginNext()
        {
            lock (SyncRoot)
            {
                if (_workflows.Count > 0)
                {
                    _currentWorkflow = _workflows.Dequeue();
                    _currentWorkflow.Begin(this);
                }
                else
                {
                    _currentWorkflow = null;
                    UpdateCompleted();
                }
            }
        }

        private void UpdateCompleted()
        {
            lock (SyncRoot)
            {
                var workflow = _currentWorkflow;

                if (workflow != null)
                {
                    Completed = (workflow.Progress.AccumulatedCompleted - StartingWeight)/Total;
                    _listener.OnProgress(this);
                }
                else
                {
                    Completed = 1.0;
                    _listener.OnProgress(_step.StepRulesName, this);
                    _listener.OnCompleted(this);
                }
            }
        }
    }
}